﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Communication.Lib.Core;
using log4net;

namespace Communication.Lib.Communication
{
    /// <summary>
    /// udp通信，应该用于接收RecvPort，,允许发送端口与接收端口一样，但不建议这么使用
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class UDPCommunicate<T> : ICommunicatable<T> where T : BaseInfo
    {
        private ILog _logger = LogManager.GetLogger(typeof(UDPCommunicate<T>));

        private readonly UdpClient udpClient;
        private DateTime _lastAliveTime;
        private Thread recvThread;
        private IProtocolable<T> protocolable;
        private bool isConnect = false;
        private bool recvStart = false;
        private bool isServer;

        #region 属性

        public string GroupIp { get; }
        public int SendPort { get; }
        public int RecvPort { get; }

        public string Name { get; set; }

        #endregion 属性

        public event Action<T> RecvHandler;

        public bool IsAlive => (DateTime.Now - _lastAliveTime).TotalSeconds <= 30;

        public UDPCommunicate(string groupIp, int sendPort, int recvPort, IProtocolable<T> protocol, bool isserver = false)
        {
            protocolable = protocol;
            GroupIp = groupIp;
            SendPort = sendPort;
            RecvPort = recvPort;

            udpClient = new UdpClient
            {
                EnableBroadcast = true
            };

            this.isServer = isserver;
            if (isserver)
            {
                recvThread = new Thread(ReceiveData)
                {
                    IsBackground = true
                };
            }
        }

        public bool SendData(T info)
        {
            try
            {
                var data = protocolable.EncodeBytes(info);

                return SendData(data);
            }
            catch (Exception e)
            {
                _logger.Error($"[{GetType().Name}] SendDataError. info:{info.ToJson()}. ex:{e}");
            }

            return false;
        }

        public bool SendData(byte[] bytes)
        {
            if (bytes == null || bytes.Length <= 0)
            {
                return false;
            }
            try
            {
                var i = udpClient?.Send(bytes, bytes.Length, GroupIp, SendPort);
                return i > 0;
            }
            catch (Exception ex)
            {
                _logger.Error($"[{GetType().Name}] SendDataError. info:{bytes.FormatAsHex()}. ex:{ex}");
            }

            return false;
        }

        public void Start()
        {
            if (isServer)
            {
                recvThread?.Start();
            }
        }

        public void Stop()
        {
            if (isServer)
            {
                recvThread?.Abort();
                recvThread = new Thread(ReceiveData)
                {
                    IsBackground = true
                };
            }
        }

        private void ReceiveData()
        {
            while (true)
            {
                recvStart = true;

                UdpClient receiveUdpClient = null;
                try
                {
                    receiveUdpClient = new UdpClient(RecvPort) { EnableBroadcast = true };
                    var groupEp = new IPEndPoint(IPAddress.Parse(GroupIp), RecvPort);
                    _logger.InfoFormat("Start {0} {1}", GroupIp, RecvPort);

                    receiveUdpClient.JoinMulticastGroup(IPAddress.Parse(GroupIp));
                    receiveUdpClient.EnableBroadcast = true;

                    while (true)
                    {
                        var receiveBytes = receiveUdpClient.Receive(ref groupEp);
                        protocolable.AddBytes(receiveBytes);
                        var obj = protocolable.DecodeData();
                        while (obj != null)
                        {
                            RecvHandler?.Invoke(obj);
                            _lastAliveTime = DateTime.Now;
                            obj = protocolable.DecodeData();
                        }
                        if (!isConnect)
                        {
                            isConnect = true;
                        }
                    }
                }
                catch (Exception err)
                {
                    _logger.Error($"[{this.GetType().Name}] RevcError.ex:{err}");
                    receiveUdpClient?.Close();
                    isConnect = false;
                    recvStart = false;
                }
                Thread.Sleep(10);
            }
        }
    }
}